package com.gitee.sergius.apitool.component;

import com.gitee.sergius.apitool.entity.*;
import com.gitee.sergius.apitool.parser.MethodAnnotationParser;
import com.gitee.sergius.apitool.annotation.Api;
import com.gitee.sergius.apitool.constant.Constant;
import com.gitee.sergius.apitool.constant.MediaType;
import com.gitee.sergius.apitool.parser.ClassAnnotationParser;
import com.gitee.sergius.apitool.tool.ClassLoaderUtil;
import org.ho.yaml.Yaml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author shawn yang
 * @version 1.0.0
 * <p><pre>初始化关键类，用来解析各个接口并生成doc文件</pre>
 *
 */
public class ApiComponent {

    private static final Logger logger = LoggerFactory.getLogger(ApiComponent.class);

    private List<String> packageScanList = new ArrayList<>();

    private String yamlFilePath;

    private static String yamlFileName = "api_doc.yaml";

    private Long batchNo = 0L;

    /**
     * 添加扫描包
     * @param packagePath 包路径，可以一次添加多个
     */
    public ApiComponent withScanPackage(String... packagePath){

        for(String path : packagePath){
            this.packageScanList.add(path);
        }

        return this;
    }

    /**
     * 设置参数解析的深度，允许的输入值：3、4、5
     * @param depth 解析深度
     */
    public ApiComponent withParseDepth(int depth){
        if(depth < 6 && depth > 2){
            Constant.PARSE_DEPTH = depth;
        }
        return this;
    }

    /**
     * 设置中间文件存储文件夹
     * @param filePath 设置生成的中间文件存贮路径
     *                 <p><pre>该参数有两个功能：1、设置中间文件的存放路径。在解析完接口之后如果配置了该选项，将会在该路径下生成一个yaml文件，文件内容是接口的描述。</pre>
     *                 <pre>2、保留历史版本。在配置了此项之后，如果接口的tag发生了变化，将保留历史版本。如果未配置，将之生成最新标签下的接口文档。</pre>
     */
    public ApiComponent withYamlFilePath(String filePath){
        this.yamlFilePath = filePath;
        return this;
    }

    /**
     * 设置中间存储文件名称
     * @param fileName 文件名称，在配置了yamlFileName(项目之后，该项用于设置中间yaml文件名称，文件以.yaml结尾，不设置将采用默认名称:api_doc.yaml
     */
    public ApiComponent withYamlFileName(String fileName){
        this.yamlFileName = fileName;
        return this;
    }

    /**
     * 设置接口访问主机地址
     * @param host 接口访问主机地址
     */
    public ApiComponent withServerHost(String host){
        Constant.API_HOST = host;
        return this;
    }


    /**
     * 解析主入口，调用该方法后将按照配置的参数开始解析生成各个接口文档
     */
    public ApiComponent build(){
        Constant.yamlEntityMap.clear();
        logger.info("Start loading api doc configuration ...");
        batchNo = System.currentTimeMillis();

        if(!StringUtils.isEmpty(yamlFilePath)) {
            loadYamlFileToMap();
        }
        for(String packagePath : packageScanList){
            List<String> classList = ClassLoaderUtil.getClassName(packagePath,true);
            if(CollectionUtils.isEmpty(classList)){
                continue;
            }
            for(String classPath : classList){
                loadApiClassConfig(classPath);
            }
        }
        //保存到yaml文件中
        if(!StringUtils.isEmpty(yamlFilePath)) {
            saveToYamlFile();
        }
        logger.info("End loading api doc configuration ...");
        return this;
    }


    private void loadApiClassConfig(String classPath) {
        logger.info("-Start loading class :" + classPath);
        List<StatusCodeEntity> parentStatusCodeList = new ArrayList<>();
        List<String> parentUrlList = new ArrayList<>();
        List<RequestMethod> parentRequestMethodList = new ArrayList<>();
        String parentTag ;
        MediaType parentMediaType = MediaType.APPLICATION_JSON_UTF8;
        Method[] methods ;
        try {
            Class<?> apiClass = Class.forName(classPath);
            ClassAnnotationParser.parseClassStatusCodeList(apiClass,parentStatusCodeList);
            ClassAnnotationParser.parseClassUrlList(apiClass,parentUrlList);
            ClassAnnotationParser.parseClassRequestMethod(apiClass,parentRequestMethodList);
            parentTag = ClassAnnotationParser.parseClassTag(apiClass);
            ClassAnnotationParser.parseClassMediaType(apiClass,parentMediaType);

            methods = apiClass.getMethods();
        } catch (ClassNotFoundException e) {
            return ;
        }

        if(methods != null && methods.length>0){
            for(Method method : methods){

                if(!method.isAnnotationPresent(Api.class)){
                    continue;
                }

                logger.info("--Start loading method :" + method.getName());

                //从Method中解析出各个注释
                String methodTag = MethodAnnotationParser.parseMethodTag(method);
                MediaType methodMediaType = MethodAnnotationParser.parseMethodMediaType(method);
                List<StatusCodeEntity> methodStatusCodeList = MethodAnnotationParser.parseMethodStatusCodeList(method);
                List<String> methodUrlList = MethodAnnotationParser.parseMethodUrlList(method,parentUrlList);
                List<RequestMethod> methodRequestMethodList = MethodAnnotationParser.parseMethodRequestMethod(method);

                String methodDesc = MethodAnnotationParser.parseMethodDesc(method);
                List<RequestParamEntity> methodRequestParamList = MethodAnnotationParser.parseMethodRequestParam(method);
                List<ResponseParamEntity> methodResponseParamList = MethodAnnotationParser.parseMethodResponseParam(method);

                //根据方法和所属类上的注释，获取真实生效的注释
                methodTag = StringUtils.isEmpty(methodTag)?parentTag:methodTag;
                methodStatusCodeList = CollectionUtils.isEmpty(methodStatusCodeList)?parentStatusCodeList:methodStatusCodeList;

                methodRequestMethodList = CollectionUtils.isEmpty(methodRequestMethodList)?parentRequestMethodList:methodRequestMethodList;
                methodMediaType = methodMediaType == null?parentMediaType:methodMediaType;

                //转换成要保存的entity列表
                List<SubEntity> subEntityList = getMethodSubEntityList(methodDesc,methodTag,methodStatusCodeList,
                        methodUrlList,methodRequestMethodList,methodMediaType,methodRequestParamList,methodResponseParamList);

                //保存到yaml缓存中
                saveToYamlMap(methodTag,subEntityList);

                logger.info("--End loading method :" + method.getName());
            }
        }

        logger.info("-End loading class :" + classPath);

    }

    /**
     * 装载yaml文件
     */
    private void loadYamlFileToMap(){
        File yamlFile = getYamlFile();
        if(yamlFile == null || yamlFile.length() == 0){
            return ;
        }
        try {
            YamlFileExtend extend = (YamlFileExtend)Yaml.loadType(yamlFile,YamlFileExtend.class);
            if(extend != null){
                List<YamlEntity> yamlEntityList = extend.getAnno();
                if(!CollectionUtils.isEmpty(yamlEntityList)){
                    loadListToMap(yamlEntityList);
                }

            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    private void loadListToMap(List<YamlEntity> yamlEntityList){
        for(YamlEntity yamlEntity : yamlEntityList){
            String tag = yamlEntity.getTag();
            Constant.yamlEntityMap.put(tag,yamlEntity);
        }
    }



    /**
     * 生成yaml文件
     */
    private void saveToYamlFile(){
        File yamlFile  = getYamlFile();
        if(yamlFile == null){
            return ;
        }

        List<YamlEntity> yamlEntityList = new ArrayList<>();

        Iterator<String> it = Constant.yamlEntityMap.keySet().iterator();
        while(it.hasNext()){
            yamlEntityList.add(Constant.yamlEntityMap.get(it.next()));
        }

        YamlFileExtend extend = new YamlFileExtend();
        extend.setAnno(yamlEntityList);

        try {
            Yaml.dump(extend, yamlFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    private File getYamlFile(){
        if(StringUtils.isEmpty(yamlFilePath) || !yamlFileName.endsWith("yaml")){
            return null;
        }

        File yamlDirectory = new File(yamlFilePath);
        if(!yamlDirectory.exists()){
            yamlDirectory.mkdirs();
        }

        String yamlPath = (yamlFilePath.endsWith(File.separator))?(yamlFilePath+yamlFileName):(yamlFilePath+File.separator+yamlFileName);
        File yamlFile = new File(yamlPath);
        if(!yamlFile.exists()){
            try {
                yamlFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return yamlFile;
    }


    private void saveToYamlMap(String methodTag,List<SubEntity> subEntityList){
        YamlEntity existYamlEntity = Constant.yamlEntityMap.get(methodTag);

        if(existYamlEntity == null){
            YamlEntity yamlEntity = new YamlEntity();
            yamlEntity.setTag(methodTag);
            yamlEntity.setSubEntityList(subEntityList);

            Constant.yamlEntityMap.put(methodTag,yamlEntity);
        }else{
            List<SubEntity> existSubEntityList = existYamlEntity.getSubEntityList();
            for(SubEntity subEntity : subEntityList){
                addNewEntityToExistList(subEntity , existSubEntityList);
            }
        }

    }

    private void addNewEntityToExistList(SubEntity subEntity , List<SubEntity> entityList){

        if(CollectionUtils.isEmpty(entityList)){
            entityList = new ArrayList<>();
            entityList.add(subEntity);
            return ;
        }else{
            boolean flag = false;
            for(int i=0,len=entityList.size();i<len;i++){
                SubEntity listEntity = entityList.get(i);
                if(listEntity.getUrl().equals(subEntity.getUrl()) && listEntity.getMethod().equals(subEntity.getMethod())){
                    entityList.set(i,subEntity);
                    flag = true;
                    break;
                }
            }
            if (!flag) {

                entityList.add(subEntity);
            }

        }
    }


    private List<SubEntity> getMethodSubEntityList(String methodDesc,
                                                   String methodTag,
                                                   List<StatusCodeEntity> methodStatusCodeList,
                                                   List<String> methodUrlList,
                                                   List<RequestMethod> methodRequestMethodList,
                                                   MediaType methodMediaType,
                                                   List<RequestParamEntity> methodRequestParamList,
                                                   List<ResponseParamEntity> methodResponseParamList){
        List<SubEntity> subEntityList = new ArrayList<>();
        for(RequestMethod requestMethod : methodRequestMethodList){
            for(String url : methodUrlList){
                SubEntity subEntity = new SubEntity();

                subEntity.setDesc(methodDesc);
                subEntity.setGenerateTime(batchNo);
                subEntity.setMethod(convertRequestMethodToString(requestMethod));
                subEntity.setReqContentType(methodMediaType.getValue());

                subEntity.setUrl(getRealUrl(url));
                subEntity.setStatusCodeEntityList(methodStatusCodeList);
                subEntity.setRequestParamEntityList(methodRequestParamList);
                subEntity.setResponseParamEntityList(methodResponseParamList);

                subEntityList.add(subEntity);
            }
        }


        return  subEntityList;
    }


    private String getRealUrl(String url){
        String realUrl ;
        if(Constant.API_HOST.endsWith("/")){
            if(url.startsWith("/")){
                realUrl = Constant.API_HOST + url.substring(1);
            }else{
                realUrl = Constant.API_HOST + url;
            }
        }else{
            if(url.startsWith("/")){
                realUrl = Constant.API_HOST + url;
            }else{
                realUrl = Constant.API_HOST +"/"+ url;
            }
        }

        return realUrl;
    }


    private String convertRequestMethodToString(RequestMethod requestMethod){
        String ret = "";
        switch(requestMethod){
            case GET:ret = "GET";break;
            case POST:ret = "POST";break;
            case PUT:ret = "PUT";break;
            case HEAD:ret = "HEAD";break;
            case PATCH:ret = "PATCH";break;
            case TRACE:ret = "TRACE";break;
            case DELETE:ret = "DELETE";break;
            case OPTIONS:ret = "OPTION";break;
        }
        return ret;
    }







}
